use anyhow::Result;
use structopt::StructOpt;
use hyper::{Client, client::HttpConnector};

use serde::{Deserialize, Serialize};

use serde_json::Value;

use crate::rpc::*;
use crate::cmd::common;
use crate::miner::SectorId;
use crate::cmd::Output;

use crate::cmd::SectorInput;

#[derive(StructOpt, Debug)]
pub enum IndexCommand {
    #[structopt(name = "declare", about = "pledge sector to workers")]
    SectorDeclare(SectorStore),
    #[structopt(name = "drop", about = "fix sector data in database")]
    SectorDrop(SectorStore),
    #[structopt(name = "find", about = "fix sector data in database")]
    SectorFind(SectorFind),
}

#[derive(StructOpt, Debug)]
pub struct SectorStore{
    #[structopt(long)]
    pub number: u64,
    #[structopt(long)]
    pub miner: u64,
    #[structopt(long)]
    pub storage: String,
    #[structopt(long = "type")]
    pub file: String,
}

#[derive(StructOpt, Debug)]
pub struct SectorFind{
    #[structopt(long)]
    pub number: u64,
    #[structopt(long)]
    pub miner: u64,
    #[structopt(long = "type")]
    pub file: String,
}

impl IndexCommand {
    pub async fn run(self) -> Result<Option<Box<dyn Output>>> {
        let client = &Client::new();
        let (token, url) = &common::parse_miner()?;

        match self {
            IndexCommand::SectorDeclare(o) => {
                miner_declare_sector(&o, client, url, token).await
            },
            IndexCommand::SectorDrop(o) => {
                miner_drop_sector(&o, client, url, token).await
            },
            IndexCommand::SectorFind(o) => {
                miner_find_sector(&o, client, url, token).await
            }
        }
    }
}

async fn miner_declare_sector(input: &SectorStore, client: &Client<HttpConnector>, url: &str, token: &str) -> Result<Option<Box<dyn Output>>> {
    let f = match input.file.as_str() {
        "unsealed" => 1,
        "sealed" => 2,
        "cache" => 4i32,
        _ => panic!("unknown file type {}", input.file),
    };

    let sec = SectorId{number: input.number, miner: input.miner};
    let sector = serde_json::to_value(sec)?;

    let params = vec![Value::from(input.storage.clone()), sector, Value::from(f), Value::from(false)];
    let req = JsonRpcRequest::with_params("Filecoin.StorageDeclareSector", params);
    let r = jsonrpc_request::<()>(client, url, token, req).await?;
    log::info!("declare sector {:?}, type {:?}, {:?}", input.number, input.file, r);
    return Ok(None)
}

async fn miner_drop_sector(input: &SectorStore, client: &Client<HttpConnector>, url: &str, token: &str) -> Result<Option<Box<dyn Output>>> {
    let f = match input.file.as_str() {
        "unsealed" => 1,
        "sealed" => 2,
        "cache" => 4i32,
        _ => panic!("unknown file type {}", input.file),
    };

    let sec = SectorId{number: input.number, miner: input.miner};
    let sector = serde_json::to_value(sec)?;

    let params = vec![Value::from(input.storage.clone()), sector, Value::from(f)];
    let req = JsonRpcRequest::with_params("Filecoin.StorageDropSector", params);
    let r = jsonrpc_request::<()>(client, url, token, req).await?;
    log::info!("drop sector {:?}, type {:?}, {:?}", input.number, input.file, r);
    return Ok(None)
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct SectorStorageInfo {
    #[serde(rename(deserialize = "ID"))]
    pub id: String,
    #[serde(rename(deserialize = "URLs"))]
    pub urls: Vec<String>,
    pub weight: u64,
    pub can_seal: bool,
    pub can_store: bool,
    pub primary: bool,
}

impl Output for Vec<SectorStorageInfo> {
    fn show(&self) -> String {
        format!("{:#?}", self)
    }
}

pub async fn miner_find_sector(sector: &SectorFind, client: &Client<HttpConnector>, url: &str, token: &str) -> Result<Option<Box<dyn Output>>>{
    let f = match sector.file.as_str() {
        "unsealed" => 1,
        "sealed" => 2,
        "cache" => 4i32,
        _ => panic!("unknown file type {}", sector.file),
    };
    let sec = SectorId{number: sector.number, miner: sector.miner};
    let sector = serde_json::to_value(sec)?;

    let params = vec![sector, Value::from(f), Value::from(0u8), Value::from(false)];
    let req = JsonRpcRequest::with_params("Filecoin.StorageFindSector", params);
    let r = jsonrpc_request::<Vec<SectorStorageInfo>>(client, url, token, req).await?;
    match r {
        Some(o) => Ok(Some(Box::new(o))), 
        None => Ok(None)
    }
}